package com.hliushi.learn.controller;

import com.hliushi.learn.exception.BusinessErrorException;
import com.hliushi.learn.exception.BusinessMsgEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

/**
 * 实现文件上传和文件下载的接口
 *
 * @author llhuang10
 * @date 2021/7/27 17:18
 */
@Slf4j
@RestController
@RequestMapping("/api")
public class FileRecourse {


    public static final String LOCAL_BASE_PATH = "D:\\downloads\\";


    /**
     * 下载文件
     * 将文件以流的形式一次性读取到内存中, 通过响应输出流输出到前端
     *
     * @param path     想要下载的文件的路径
     * @param response response
     */
    @GetMapping("/download")
    public void download(@RequestParam String path, HttpServletResponse response) {
        try {
            // path是指想要下载的文件路径
            // File file = new File("src/main/resources/temp/" + path);
            File file = new File(LOCAL_BASE_PATH + path);
            log.info(file.getPath());

            // 获取文件名
            String fileName = file.getName();
            // 获取文件后缀名
            String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
            log.info("文件后缀名: {}", ext);

            // 将文件写入输入流
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bfs = new BufferedInputStream(fis);

            // fis.available() 返回可以从此输入流读取 (或跳过)的剩余字节数的估计值
            byte[] buffer = new byte[fis.available()];
            log.info("fis.available() = {}", fis.available());

            /**
             * 将文件以流的形式一次性读取到内存中
             */
            bfs.read(buffer);
            fis.close();

            // 清空response的缓冲区中存在的任何数据以及状态代码和标题
            response.reset();
            // 设置response的Header
            response.setCharacterEncoding("UTF-8");


            /**
             *
             * Content-Disposition的作用: 告知浏览器以何种方式显示响应返回的文件,
             * 用浏览器打开还是以附件的形式下载到本地保存
             * attachment表示以附件方式下载, inline表示在线打开,  "Content-Disposition: inline; filename=文件名.mp3"
             * filename表示文件的默认名称, 因为网络传输只支持URL编码的相关原因,
             * 因为需要将文件名URL编码后进行传输, 前端接收到后需要反编码才能获取真正的名称
             */
            // response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            // 2021年7月27日19:11:28, edge浏览器并没有在线阅览 png图片格式, 而是下载文件了
            response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            // 告知浏览器文件的大小
            response.addHeader("Content-Length", "" + file.length());

            BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

            // application/octet-stream指任意类型的二进制流数据, 一般用于下载文件
            // octet 八重奏, 在这里指八个byte
            response.setContentType("application/octet-stream");
            outputStream.write(buffer);

            // 刷新这个缓冲的输出流, 这会强制将任何缓冲的输出字节写出到底层输出流
            outputStream.flush();

        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 下载文件: 将输入流中的数据循环写入到输出流中, 而不是一次性读取到内存
     *
     * @param path     指想要下载的文件路径
     * @param response response
     */
    @GetMapping("/downloadLocal")
    public void downloadLocal(String path, HttpServletResponse response) {
        String realPath = LOCAL_BASE_PATH + path;
        // 读到流中
        try (FileInputStream inputStream = new FileInputStream(realPath);
             OutputStream outputStream = response.getOutputStream()) {
            response.reset();
            response.setContentType("application/octet-stream");
            String fileName = new File(realPath).getName();
            response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));

            byte[] bytes = new byte[1024];
            int len;
            // 从输入流中读取一定数量的字节[一次读入往字节数组1024个字节], 并将其存储在缓冲区字节数组中, 读到末尾返回-1
            while ((len = inputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 网络文件下载到服务器本地
     *
     * @param netAddress 文件所在网络地址
     * @param path       下载后的文件路径和名称
     */
    @GetMapping("/downloadNet")
    public void downloadNet(String netAddress, String path) {
        URLConnection conn;
        try {
            URL url = new URL(netAddress);
            conn = url.openConnection();
            try (InputStream inputStream = conn.getInputStream();
                 FileOutputStream fileOutputStream = new FileOutputStream(LOCAL_BASE_PATH + path)) {
                int byteSum = 0;
                int len;

                byte[] buffer = new byte[1024];
                while ((len = inputStream.read(buffer)) > 0) {
                    byteSum += len;
                    log.info("byteSum = {}", byteSum);
                    fileOutputStream.write(buffer, 0, len);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 网络文件获取到服务器后, 经过服务器处理后响应给前端
     *
     * @param netAddress 文件所在网络地址
     * @param filename   文件名称
     * @param isOnline   是否在线打开
     * @param response   response
     */
    @GetMapping("/netDownLoadNet")
    public void netDownLoadNet(String netAddress, String filename, boolean isOnline, HttpServletResponse response) {
        URLConnection conn;
        try {
            URL url = new URL(netAddress);
            conn = url.openConnection();
            try (InputStream inputStream = conn.getInputStream();
                 OutputStream outputStream = response.getOutputStream()) {

                response.reset();
                response.setContentType(conn.getContentType());
                log.info("ContentType = {}", conn.getContentType());
                if (isOnline) {
                    // 在线打开方式, 文件名应该编码成UTF-8
                    response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(filename, "UTF-8"));
                } else {
                    // 纯下载方式 文件名应该编码成UTF-8
                    response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
                }

                int byteSum = 0;
                int len;
                byte[] buffer = new byte[1024];
                while ((len = inputStream.read(buffer)) > 0) {
                    byteSum += len;
                    log.info("byteSum = {}", byteSum);
                    outputStream.write(buffer, 0, len);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 单文件上传
     *
     * @param id   id
     * @param file 文件
     * @return 成功响应success
     */
    @PostMapping("/upload")
    public ResponseEntity<String> singleUpload(@RequestParam String id, @RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            throw new BusinessErrorException(BusinessMsgEnum.FILE_IS_EMPTY);
        }

        log.info("id = {}", id);
        // 返回客户端文件系统中的原始文件名
        String filename = file.getOriginalFilename();
        String contentType = file.getContentType();
        File destPath = new File(LOCAL_BASE_PATH + filename);
        log.info("filename = {}, ContentType = {}", filename, contentType);
        try {
            // 如果命名文件不存在且已成功创建，则为true ； 如果指定文件已存在，则为false
            boolean newFile = destPath.createNewFile();
            if (!newFile) {
                // 命名文件已经存在了, 该怎么处理 时间
                log.info("{} 命名文件已经存在了", destPath.getName());
            }
            file.transferTo(destPath);
            log.info("上传成功!");
            return ResponseEntity.ok("上传成功");
        } catch (IOException e) {
            log.error(e.toString(), e);
        }

        return ResponseEntity.ok("上传失败");
    }


    /**
     * 上传多个文件
     *
     * @param request HttpServletRequest
     * @return 正确 上传成功  错误返回 第几个文件上传失败
     */
    @PostMapping("/multi-upload")
    public ResponseEntity<String> multiUpload(HttpServletRequest request) {
        List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");

        for (int i = 0; i < files.size(); i++) {
            MultipartFile file = files.get(i);
            int index = i + 1;
            if (file.isEmpty()) {
                log.error("上传第{}个文件失败", index);
                return ResponseEntity.ok("上传第" + index + "个文件失败");
            }

            String filename = file.getOriginalFilename();
            String contentType = file.getContentType();

            log.info("filename = {}, ContentType = {}", filename, contentType);

            File destPath = new File(LOCAL_BASE_PATH + filename);

            try {
                file.transferTo(destPath);
                log.info("上传第{}个文件成功", index);
            } catch (IOException e) {
                log.error(e.toString(), e);
            }

        }

        return ResponseEntity.ok("上传成功");
    }


    /**
     * 上传多个文件
     * 使用MultiFile[]数组形式形式
     *
     * @param files MultipartFile[]数组
     * @return 正确 上传成功  错误返回 第几个文件上传失败
     */
    @PostMapping("/multi-upload2")
    public ResponseEntity<String> multiUpload2(@RequestParam("file") MultipartFile[] files) {
        // 使用 org.apache.commons.lang3.ArrayUtils 判断数组是否为空
        if (ArrayUtils.isEmpty(files)) {
            throw new BusinessErrorException(BusinessMsgEnum.FILE_IS_EMPTY);
        }
        // if (files == null || files.length == 0) {
        //     throw new BusinessErrorException(BusinessMsgEnum.FILE_IS_EMPTY);
        // }

        for (int i = 0; i < files.length; i++) {
            MultipartFile file = files[i];
            int index = i + 1;
            if (file.isEmpty()) {
                log.error("上传第{}个文件失败", index);
                return ResponseEntity.ok("上传第" + index + "个文件失败");
            }

            String filename = file.getOriginalFilename();
            String contentType = file.getContentType();

            log.info("filename = {}, ContentType = {}", filename, contentType);

            File destPath = new File(LOCAL_BASE_PATH + filename);

            try {
                file.transferTo(destPath);
                log.info("上传第{}个文件成功", index);
            } catch (IOException e) {
                log.error(e.toString(), e);
            }

        }

        return ResponseEntity.ok("上传成功");
    }


    /**
     * 上传文件时, 选择二进制流的的方式, 而不是选择表单
     *
     * @param request request
     * @return
     */
    @PostMapping("/octet-upload")
    public ResponseEntity<String> uploadOctetStream(HttpServletRequest request) {
        // request.getHeader();

        String filename = "test.log";
        Path dest = Paths.get(LOCAL_BASE_PATH + filename);

        log.info("{}", request);
        try {
            FileCopyUtils.copy(request.getInputStream(), Files.newOutputStream(dest));
        } catch (IOException e) {
            log.error(e.toString(), e);
            return ResponseEntity.ok("上传失败");
        }
        return ResponseEntity.ok("上传成功");
    }
}

